package org.canaan.quartz;

import org.apache.commons.lang3.StringUtils;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.impl.JobDetailImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.support.ArgumentConvertingMethodInvoker;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 @Author Canaan
 @Date 2018/3/16 */
public class JobDetailBuilderFactory implements BeanClassLoaderAware, ApplicationContextAware, InitializingBean {
    private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
    private ApplicationContext             applicationContext;
    private List<JobDetailExecuteObserver> observers;

    public JobDetailBuilder getBuilder() {
        return new JobDetailBuilderImpl(applicationContext, beanClassLoader, observers);
    }

    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        //创建观察者
        observers = new ArrayList<>();
        Map<String, JobDetailExecuteObserver> observersMap = applicationContext.getBeansOfType(JobDetailExecuteObserver.class);
        if (observersMap == null || observersMap.isEmpty()) {
            return;
        }
        for (String beanName : observersMap.keySet()) {
            observers.add(observersMap.get(beanName));
        }
    }

    /**
     构建器
     */
    private static class JobDetailBuilderImpl extends ArgumentConvertingMethodInvoker implements JobDetailBuilder {
        private String name;
        private String  group      = "DEFAULT";
        private boolean concurrent = true;
        private String                         targetBeanName;
        private ClassLoader                    beanClassLoader;
        private ApplicationContext             applicationContext;
        private List<JobDetailExecuteObserver> observers;

        private JobDetailBuilderImpl(ApplicationContext applicationContext, ClassLoader beanClassLoader, List<JobDetailExecuteObserver> observers) {
            this.applicationContext = applicationContext;
            if (beanClassLoader != null) {
                this.beanClassLoader = beanClassLoader;
            }
            this.observers = observers;
        }

        public JobDetailBuilder setName(String name) {
            this.name = name;
            return this;
        }

        public JobDetailBuilder setGroup(String group) {
            this.group = group;
            return this;
        }

        public JobDetailBuilder setConcurrent(boolean concurrent) {
            this.concurrent = concurrent;
            return this;
        }

        public JobDetailBuilder setTargetBeanName(String targetBeanName) {
            this.targetBeanName = targetBeanName;
            return this;
        }

        public JobDetail build(String key) {
            String name = this.name;
            Class jobClass = this.concurrent ? JobDetailBuilder.MethodInvokingJob.class : JobDetailBuilder.StatefulMethodInvokingJob.class;
            JobDetailImpl jdi = new JobDetailImpl();
            jdi.setName(name);
            jdi.setGroup(this.group);
            jdi.setJobClass(jobClass);
            jdi.setDurability(true);
            if (StringUtils.isNotBlank(this.group)) {
                jdi.setKey(new JobKey(key, this.group));
            } else {
                jdi.setKey(new JobKey(key));
            }
            jdi.getJobDataMap().put("methodInvoker", this);
            jdi.getJobDataMap().put("observers", this.observers);
            return jdi;
        }

        protected Class<?> resolveClassName(String className) throws ClassNotFoundException {
            return ClassUtils.forName(className, this.beanClassLoader);
        }

        public Class<?> getTargetClass() {
            Class targetClass = super.getTargetClass();
            if (targetClass == null && this.targetBeanName != null) {
                Assert.state(this.applicationContext != null, "BeanFactory must be set when using \'targetBeanName\'");
                targetClass = this.applicationContext.getType(this.targetBeanName);
            }

            return targetClass;
        }

        public Object getTargetObject() {
            Object targetObject = super.getTargetObject();
            if (targetObject == null && this.targetBeanName != null) {
                Assert.state(this.applicationContext != null, "BeanFactory must be set when using \'targetBeanName\'");
                targetObject = this.applicationContext.getBean(this.targetBeanName);
            }

            return targetObject;
        }
    }

}
